package com.rightrule.service.impl;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.rightrule.dao.DecisionStrategyWaterDao;
import com.rightrule.dao.DecisionTestStrategyWaterDao;
import com.rightrule.entity.DecisionRuleWaterEntity;
import com.rightrule.entity.DecisionStrategyWaterEntity;
import com.rightrule.entity.DecisionTestRuleWaterEntity;
import com.rightrule.entity.DecisionTestStrategyWaterEntity;
import com.rightrule.rule.component.LoadPackageSnapshot;
import com.rightrule.rule.constans.CommonConstant;
import com.rightrule.rule.constans.StrategyDivisionTypeEnum;
import com.rightrule.rule.constans.TestStrategyWaterStatusEnum;
import com.rightrule.rule.constans.TestTaskTypeEnum;
import com.rightrule.rule.dto.*;
import com.rightrule.rule.executor.RuleFlow;
import com.rightrule.rule.executor.RuntimeContext;
import com.rightrule.rule.response.FlowExecutionResponse;
import com.rightrule.service.DecisionRuleWaterService;
import com.rightrule.service.DecisionTestRuleWaterService;
import com.rightrule.service.StrategyDecisionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class StrategyDecisionServiceImpl implements StrategyDecisionService {

    @Autowired
    private LoadPackageSnapshot loadPackageSnapshot;

    @Resource
    private DecisionStrategyWaterDao decisionStrategyWaterDao;

    @Autowired
    private DecisionRuleWaterService decisionRuleWaterService;

    @Resource
    private DecisionTestStrategyWaterDao decisionTestStrategyWaterDao;

    @Autowired
    private DecisionTestRuleWaterService decisionTestRuleWaterService;


    private static final Integer DIVISION_RANG_LEN = 2;


    /**
     * 决策执行
     *
     * @param commonDecisionReq
     * @return
     */
    @Override
    public DecisionResultDTO decision(CommonDecisionDTO commonDecisionReq) {
        long start = System.nanoTime();
        log.info("decision req={}", commonDecisionReq);
        DecisionResultDTO decisionResultDTO = new DecisionResultDTO();

        Map<String, RuleFlow> onlineSnapshotCacheLogic = loadPackageSnapshot.getOnlineSnapshotCacheLogic();
        RuleFlow ruleFlow = onlineSnapshotCacheLogic.get(commonDecisionReq.getStrategyCode() + "_" + commonDecisionReq.getStrategyVersionId());

        // 如果缓存没有则触发远程拉取逻辑
        if (Objects.isNull(ruleFlow)) {
            loadPackageSnapshot.initAllOnLinePackageSnapshot(commonDecisionReq.getStrategyCode());
            ruleFlow = onlineSnapshotCacheLogic.get(commonDecisionReq.getStrategyCode() + "_" + commonDecisionReq.getStrategyVersionId());
        }

        RuntimeContext runtimeContext = new RuntimeContext();
        runtimeContext.setStrategyVersionId(commonDecisionReq.getStrategyVersionId());
        runtimeContext.setBusinessId(commonDecisionReq.getBusinessId());
        runtimeContext.setStrategyId(ruleFlow.getStrategyId());
        runtimeContext.setIpid(commonDecisionReq.getIpId());

        // 决策
        FlowExecutionResponse executionResponse;
        // todo 如果需要mock则走mock逻辑
        // 当前默认走真实逻辑
        executionResponse = (FlowExecutionResponse) ruleFlow.execute(runtimeContext, commonDecisionReq.getSessionData());

        decisionResultDTO.setResultData(executionResponse.getOutput());
        decisionResultDTO.setHitRules(executionResponse.getHitRules());
        // 如果决策结果为空则抛异常
        if (CollectionUtils.isEmpty(decisionResultDTO.getResultData())) {
            throw new RuntimeException("decision result is empty");
        }

        // 保存决策流水
        decisionWaterReport(executionResponse, commonDecisionReq, ruleFlow.getStrategyId());

        // 检查是否需要旁路测试，如果是需要旁路测试，则需要把当前的真实决策入参给到需要旁路测试的版本执行一遍
        doBypathDecision(commonDecisionReq, runtimeContext, executionResponse);

        log.info("decision response={},elapsed time ={}", decisionResultDTO, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));
        return decisionResultDTO;
    }


    /**
     * 上报决策流水
     *
     * @param executionResponse
     * @param commonDecisionReq
     * @param strategyId
     */
    private void decisionWaterReport(FlowExecutionResponse executionResponse, CommonDecisionDTO commonDecisionReq, Long strategyId) {

        DecisionStrategyWaterEntity strategyWaterEntity = new DecisionStrategyWaterEntity();

        // 设置决策结果属性
        strategyWaterEntity.setStrategyVersionId(commonDecisionReq.getStrategyVersionId());
        strategyWaterEntity.setStrategyId(strategyId);
        strategyWaterEntity.setBusinessId(commonDecisionReq.getBusinessId());
        strategyWaterEntity.setIpId(commonDecisionReq.getIpId());

        // todo 等待若茵完成随机序列号
        strategyWaterEntity.setRequestSeq(UUID.randomUUID().toString());
        strategyWaterEntity.setInputParams(JSONObject.toJSONString(commonDecisionReq.getSessionData()));
        strategyWaterEntity.setOutputResult(JSONObject.toJSONString(executionResponse.getOutput()));

        int insert = decisionStrategyWaterDao.insert(strategyWaterEntity);
        if (insert > 0) {
            List<DecisionRuleWaterDTO> ruleExeWaters = executionResponse.getRuleExeWaters();
            List<DecisionRuleWaterEntity> decisionRuleWaterEntities = ruleExeWaters.stream().map(item -> {

                DecisionRuleWaterEntity decisionRuleWaterEntity = new DecisionRuleWaterEntity();
                BeanUtils.copyProperties(item, decisionRuleWaterEntity);
                decisionRuleWaterEntity.setStrategyVersionId(strategyWaterEntity.getId());
                return decisionRuleWaterEntity;
            }).collect(Collectors.toList());

            decisionRuleWaterService.saveBatch(decisionRuleWaterEntities);
        }
    }


    /**
     * 判断是否需要旁路测试如果需要则执行
     *
     * @param commonDecisionReq
     * @param runtimeContext
     * @param executionResponse
     */
    private void doBypathDecision(CommonDecisionDTO commonDecisionReq, RuntimeContext runtimeContext, FlowExecutionResponse executionResponse) {
        Map<String, StrategyDivisionSnapshotDTO> onlineBypathDivisionCacheLogic = loadPackageSnapshot.getOnlineBypathDivisionCacheLogic();
        StrategyDivisionSnapshotDTO divisionSnapshotDTO = onlineBypathDivisionCacheLogic.get(commonDecisionReq.getStrategyCode());

        // 如果不为空则表示需要旁路测试
        if (Objects.nonNull(divisionSnapshotDTO)) {
            // 判断是否在旁路时间内
            Date nowTime = new Date();
            if (nowTime.after(divisionSnapshotDTO.getStartTime())) {
                // 获取对应的旁路测试版本
                RuleFlow bypathRuleFlow = getBypathRuleFlow(divisionSnapshotDTO, commonDecisionReq);

                if (Objects.nonNull(bypathRuleFlow)) {
                    FlowExecutionResponse bypathResponse = (FlowExecutionResponse) bypathRuleFlow.execute(runtimeContext, commonDecisionReq.getSessionData());

                    DecisionTestCaseDecisionResultDTO decisionTestCaseDecisionResultDTO = new DecisionTestCaseDecisionResultDTO();
                    DecisionTestStrategyWaterDTO testStrategyWaterDTO = new DecisionTestStrategyWaterDTO();

                    // 设置旁路决策基础属性
                    testStrategyWaterDTO.setStrategyVersionId(bypathRuleFlow.getStrategyVersionId());
                    testStrategyWaterDTO.setStrategyId(divisionSnapshotDTO.getStrategyId());
                    testStrategyWaterDTO.setBusinessId(commonDecisionReq.getBusinessId());
                    testStrategyWaterDTO.setIpId(commonDecisionReq.getIpId());
                    testStrategyWaterDTO.setDeployId(divisionSnapshotDTO.getDeployId().toString());
                    testStrategyWaterDTO.setTaskId(divisionSnapshotDTO.getTaskId().toString());
                    testStrategyWaterDTO.setTaskType(TestTaskTypeEnum.BYPATH.getCode());

                    // 设置旁路原决策结果
                    testStrategyWaterDTO.setSrcInputParams(JSONObject.toJSONString(commonDecisionReq.getSessionData()));
                    testStrategyWaterDTO.setSrcOutputResult(JSONObject.toJSONString(executionResponse.getOutput()));

                    // 设置旁路新决策结果
                    testStrategyWaterDTO.setStatus(TestStrategyWaterStatusEnum.ERROR.getCode());
                    if (Objects.nonNull(bypathResponse)) {

                        testStrategyWaterDTO.setStatus(TestStrategyWaterStatusEnum.FINISH.getCode());
                        if (!CommonConstant.EXECUTE_SUCCESS.equals(bypathResponse.getExecuteStaus())) {
                            testStrategyWaterDTO.setStatus(TestStrategyWaterStatusEnum.ERROR.getCode());
                        }

                        if (!CollectionUtils.isEmpty(bypathResponse.getOutput())) {
                            testStrategyWaterDTO.setNewOutputResult(JSONObject.toJSONString(bypathResponse.getOutput()));
                        }

                        // 保存决策流水
                        testStrategyWaterDTO.setRuleExeWaters(bypathResponse.getRuleExeWaters());
                    }

                    // 如果旁路测试结果已经超时
                    if (nowTime.after(divisionSnapshotDTO.getEndTime())) {
                        // 删除当前策略的旁路测试配置，并且将当前的旁路测试结果标识为最后一个
                        onlineBypathDivisionCacheLogic.remove(commonDecisionReq.getStrategyCode());
                        testStrategyWaterDTO.setLastTag(true);
                    }

                    decisionTestCaseDecisionResultDTO.setDecisionTestStrategyWaterDTO(testStrategyWaterDTO);
                    // 统计测试报告
                    testResultReport(decisionTestCaseDecisionResultDTO);
                }
            }
        }
    }

    /**
     * 统计测试报告 todo 后续这个方法抽成公共报告处理方法,并且异步去执行,当前只处理旁路测试报告
     *
     * @param testCaseDecisionResultDTO
     */
    private void testResultReport(DecisionTestCaseDecisionResultDTO testCaseDecisionResultDTO) {
        DecisionTestStrategyWaterDTO decisionTestStrategyWaterDTO = testCaseDecisionResultDTO.getDecisionTestStrategyWaterDTO();

        if (TestTaskTypeEnum.BYPATH.getCode().equals(decisionTestStrategyWaterDTO.getTaskType())) {
            // 多个线程异步执行的情况下 当最后一条流水时需要睡眠等待其他流水完成后，再停机
            if (decisionTestStrategyWaterDTO.isLastTag()) {
                try {
                    Thread.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 保存策略测试流水
            DecisionTestStrategyWaterEntity testStrategyWaterEntity = new DecisionTestStrategyWaterEntity();
            BeanUtils.copyProperties(decisionTestStrategyWaterDTO, testStrategyWaterEntity);
            decisionTestStrategyWaterDao.insert(testStrategyWaterEntity);

            decisionTestStrategyWaterDTO.setId(testStrategyWaterEntity.getId());

            // 保存测试规则流水
            List<DecisionRuleWaterDTO> ruleExeWaters = decisionTestStrategyWaterDTO.getRuleExeWaters();
            List<DecisionTestRuleWaterEntity> ruleWaterEntityList = ruleExeWaters.stream().map(item -> {
                DecisionTestRuleWaterEntity ruleWaterEntity = new DecisionTestRuleWaterEntity();
                BeanUtils.copyProperties(item, ruleWaterEntity);
                ruleWaterEntity.setStrategyWaterId(decisionTestStrategyWaterDTO.getId());
                ruleWaterEntity.setTaskId(Long.valueOf(decisionTestStrategyWaterDTO.getTaskId()));
                return ruleWaterEntity;
            }).collect(Collectors.toList());

            decisionTestRuleWaterService.saveBatch(ruleWaterEntityList);

        }

        // todo 新建测试任务的时候会落测试任务标,这里已经测试报告已出，需要将测试任务状态改为成功
    }

    /**
     * 获取对应的旁路测试包
     *
     * @param bypathDivisionConfig
     * @param commonDecisionReq
     * @return
     */
    private RuleFlow getBypathRuleFlow(StrategyDivisionSnapshotDTO bypathDivisionConfig, CommonDecisionDTO commonDecisionReq) {
        // 不分流量，直接取离线版本
        Map<String, RuleFlow> offlineSnapshotCacheLogic = loadPackageSnapshot.getOfflineSnapshotCacheLogic();

        if (StrategyDivisionTypeEnum.NO_DIVISION.getCode().equals(bypathDivisionConfig.getDivisionType())) {
            DecisionStrategyDivisionConfigDTO divisionConfigDTO = bypathDivisionConfig.getDivisionConfigs().get(0);

            return offlineSnapshotCacheLogic.get(bypathDivisionConfig.getStrategyCode() + "_" + divisionConfigDTO.getStrategyVersionId());
        }
        // 按key分流
        else if (StrategyDivisionTypeEnum.KEY_DIVISION.getCode().equals(bypathDivisionConfig.getDivisionType())
                && !CollectionUtils.isEmpty(bypathDivisionConfig.getDivisionConfigs())) {

            DecisionStrategyDivisionConfigDTO divisionConfigDTO = bypathDivisionConfig.getDivisionConfigs().get(0);
            String divisionFieldValueStr = commonDecisionReq.getSessionData().get(divisionConfigDTO.getDivisionField()).toString();

            // 检查是否符合流量配置
            if (isMatchDivisionConfig(divisionFieldValueStr, divisionConfigDTO)) {
                return offlineSnapshotCacheLogic.get(bypathDivisionConfig.getStrategyCode() + "_" + divisionConfigDTO.getStrategyVersionId());
            }

        }
        // 随机分配
        else if (StrategyDivisionTypeEnum.RANDOM_DIVISION.getCode().equals(bypathDivisionConfig.getDivisionType())
                && !CollectionUtils.isEmpty(bypathDivisionConfig.getDivisionConfigs())) {

            DecisionStrategyDivisionConfigDTO divisionConfigDTO = bypathDivisionConfig.getDivisionConfigs().get(0);

            Random random = new Random();
            Integer randomNum = random.nextInt(100);

            Integer start = divisionConfigDTO.getDivisionRange().get(0);
            Integer end = divisionConfigDTO.getDivisionRange().get(1);

            if (randomNum >= start && randomNum <= end) {
                return offlineSnapshotCacheLogic.get(bypathDivisionConfig.getStrategyCode() + "_" + divisionConfigDTO.getStrategyVersionId());
            }
        }

        return null;
    }


    /**
     * 判断是否通过业务主键匹配对应的版本
     *
     * @param divisionFieldValueStr
     * @param versionDivisionConfig
     * @return
     */
    private boolean isMatchDivisionConfig(String divisionFieldValueStr, DecisionStrategyDivisionConfigDTO versionDivisionConfig) {
        List<Integer> divisionRange = versionDivisionConfig.getDivisionRange();
        List<Integer> divisionIndex = versionDivisionConfig.getFieldIndex();

        // 检测配置是否正确
        if (!CollectionUtils.isEmpty(divisionRange) && DIVISION_RANG_LEN.equals(divisionRange.size())
                && DIVISION_RANG_LEN.equals(divisionIndex.size())
                && divisionFieldValueStr.length() > divisionIndex.get(DIVISION_RANG_LEN - 1)
        ) {

            Integer firstRang = versionDivisionConfig.getDivisionRange().get(0);
            Integer secondRang = versionDivisionConfig.getDivisionRange().get(1);

            Integer firstIndex = versionDivisionConfig.getFieldIndex().get(0);
            Integer secondIndex = versionDivisionConfig.getFieldIndex().get(1);

            // 取出key对应的的分流下标
            String fieldValueStr = divisionFieldValueStr.substring(firstIndex, firstIndex + 1) + divisionFieldValueStr.substring(secondIndex, secondIndex + 1);
            // 如果是纯数字则直接判断
            if (StringUtils.isNumber(fieldValueStr)) {
                Integer divisionValueInt = Integer.parseInt(fieldValueStr);
                if (divisionValueInt > firstRang && divisionValueInt <= secondRang) {
                    return true;
                }
            }
            // 如果是字符串
            else {
                String hexFieldValue = DigestUtils.md5Hex(fieldValueStr);
                int hexFieldValueInt = Integer.parseInt(hexFieldValue.substring(hexFieldValue.length() - 2, hexFieldValue.length()), 16);
                Integer divisionValueInt = hexFieldValueInt % 100;
                if (divisionValueInt >= firstRang && divisionValueInt <= secondRang) {
                    return true;
                }
            }
        }
        return false;
    }
}
